package com.caffeinatedbliss.nanowrimo;

import java.util.*;

public class NanowrimoComputer {

  public static final int DAYS_IN_NANO = 30;

  //    protected String historyParameter;
  protected int historyCounts[];
  protected int pace[];
  protected boolean boundries[];
  protected int endDay;
  protected int current;
  protected double percentComplete;
  protected int daysLeftNaNoWriMo;
  protected int expectedPerDayAmount[];
  protected int expectedWordCount[];
  protected int paceExpected[];
  protected int wordcount[];
  protected boolean outliers[];
  protected int maxAmount;
  protected int paceExpectedTotal;
  protected int currentDay;
  protected int numberObservations;
  protected int monthlyGoal;
  protected int populationMean;
  private int[] adjustedCount;

  public NanowrimoComputer(String historyParameter, int goal) {
    historyParameter = (historyParameter == null) ? " " : historyParameter;

    monthlyGoal = goal;

    computeBasicStatistics(historyParameter);
    detectPopulationOutliers();
    computePercentComplete();
    computeDaysLeft();
    computePerDayAmount();
//
//    new PlainTextTabulator().tabulateResults(this);
  }

  public NanowrimoComputer(int[] amounts, int goal) {
    monthlyGoal = goal;

    computeBasicStatistics(amounts);
    detectPopulationOutliers();
    computePercentComplete();
    computeDaysLeft();
    computePerDayAmount();
//
//    new PlainTextTabulator().tabulateResults(this);
  }

  private void computeBasicStatistics(int[] amounts) {
    current = 0;
    endDay = 0;
    int count = 0;
    numberObservations = amounts.length;
    createArrays(numberObservations);

    for (int i = 0; i < historyCounts.length && count < DAYS_IN_NANO; i++) {
      historyCounts[i] = amounts[i];
      int previous = current;
      current += historyCounts[i];
      wordcount[i] = current;
      boundries[i] = ((previous / 10000 + 1) == (current / 10000));
      count++;
      pace[i] = current / count;
    }
  }

  private void computeBasicStatistics(String historyParameter) {
    current = 0;
    endDay = 0;
    int count = 0;
    StringTokenizer tokenizer = new StringTokenizer(historyParameter, " ,");
    numberObservations = tokenizer.countTokens();
    createArrays(numberObservations);

    for (int i = 0; i < historyCounts.length && count < DAYS_IN_NANO; i++) {
      historyCounts[i] = Integer.parseInt(tokenizer.nextToken());
      int previous = current;
      current += historyCounts[i];
      wordcount[i] = current;
      boundries[i] = ((previous / 10000 + 1) == (current / 10000));
      count++;
      pace[i] = current / count;
    }
  }

  private double computeCorrelation(int skip) {
    double sy = 0;
    double sx = 0;
    double sxx = 0;
    double syy = 0;
    double sxy = 0;
    double n = wordcount.length;
    for (int i = 0; i < n; i++) {
      if (i != skip) {
        sx += (i + 1);
        sxx += (i + 1) * (i + 1);
        syy += wordcount[i] * wordcount[i];
        sxy += (i + 1) * wordcount[i];
        sy += wordcount[i];
      } else {
        n--;
      }
    }

    double top = (n * sxy - sx * sy);
    double bottomX = (n * sxx - sx * sx);
    double bottomY = (n * syy - sy * sy);
    return top / (Math.sqrt(bottomX) * Math.sqrt(bottomY));
  }

  private void detectPopulationOutliers() {
    int sxx = 0;
    for (int i = 0; i < historyCounts.length; i++) {
      sxx += historyCounts[i] * historyCounts[i];
    }

    int initialSumX = current;
    int initialSumXX = sxx;
    double theCount = ((double) historyCounts.length - 1);
    ArrayList outliersList = new ArrayList();
    for (int i = 0; i < historyCounts.length; i++) {
      int dailyValue = historyCounts[i];
      int sumx = initialSumX - dailyValue;
      int sumxx = initialSumXX - (dailyValue * dailyValue);
      double mean = (double) sumx / theCount;
      double stdDev = Math.sqrt((double) sumxx / theCount - mean * mean);
      int range = (int) (stdDev * 2);
      int minOutlier = (int) Math.floor(mean - range);
      int maxOutlier = (int) Math.ceil(mean + range);
      outliers[i] = (dailyValue <= minOutlier || dailyValue >= maxOutlier);
      if (outliers[i]) {
        theCount--;
        initialSumX -= dailyValue;
        initialSumXX -= (dailyValue * dailyValue);
        outliersList.add(new Integer(dailyValue));
      }
      endDay = 1 + (int) Math.ceil(((double) monthlyGoal - (double) current) / (double) pace[i]);
    }

    populationMean = (int) Math.ceil((double) initialSumX / (double) theCount);
    adjustedCount = new int[historyCounts.length];
    for (int i = 0; i < historyCounts.length; i++) {
      int historyCount = historyCounts[i];
      adjustedCount[i] = (outliers[i]) ? populationMean : historyCount;
    }

    double total = 0;
    int expTotal = 0;
    for (int i = 0; i < adjustedCount.length; i++) {
      total += adjustedCount[i];
      pace[i] = (int) Math.ceil(total / (double) (i + 1));
      expTotal += pace[i];
      paceExpected[i] = expTotal;
    }
  }

  private void computePerDayAmount() {
    int remaining = monthlyGoal;
    int daysLeft = DAYS_IN_NANO;
    maxAmount = 0;
    int runningTotal = 0;
    for (int i = 0; i < expectedPerDayAmount.length; i++) {
      expectedPerDayAmount[i] = remaining / daysLeft;
      maxAmount = Math.max(maxAmount, expectedPerDayAmount[i]);
      runningTotal += expectedPerDayAmount[i];
      expectedWordCount[i] = runningTotal;
      if (i < historyCounts.length) {
        remaining -= historyCounts[i];
        maxAmount = Math.max(maxAmount, historyCounts[i]);
      } else {
        remaining -= expectedPerDayAmount[i];
      }
      daysLeft--;
    }
  }

  private void computePercentComplete() {
    percentComplete = ((double) current * 100.0) / (double) monthlyGoal;
    if (percentComplete > 100.0) {
      percentComplete = 100.0;
    }
  }

  private void createArrays(int numberObservations) {
    historyCounts = new int[numberObservations];
    wordcount = new int[numberObservations];
    pace = new int[DAYS_IN_NANO];
    expectedPerDayAmount = new int[DAYS_IN_NANO];
    expectedWordCount = new int[DAYS_IN_NANO];
    boundries = new boolean[DAYS_IN_NANO];
    paceExpected = new int[DAYS_IN_NANO];
    outliers = new boolean[DAYS_IN_NANO];
  }

  private void computeDaysLeft() {
    GregorianCalendar calendar = new GregorianCalendar();
    calendar.setTime(new Date(System.currentTimeMillis()));
    int month = calendar.get(2);
    int day = calendar.get(5);
    if (month < 10) {
      daysLeftNaNoWriMo = DAYS_IN_NANO;
      currentDay = 1;
    } else if (month == 10) {
      daysLeftNaNoWriMo = 31 - day;
      currentDay = day;
    } else if (month > 10) {
      daysLeftNaNoWriMo = 0;
      currentDay = DAYS_IN_NANO;
    }
  }

  public int[] getHistoryCounts() {
    return historyCounts;
  }

  public int[] getPace() {
    return pace;
  }

  public boolean[] getBoundries() {
    return boundries;
  }

  public int getEndDay() {
    return endDay;
  }

  public int getCurrent() {
    return current;
  }

  public double getPercentComplete() {
    return percentComplete;
  }

  public int getDaysLeftNaNoWriMo() {
    return daysLeftNaNoWriMo;
  }

  public int[] getExpectedPerDayAmount() {
    return expectedPerDayAmount;
  }

  public int[] getPaceExpected() {
    return paceExpected;
  }

  public boolean[] getOutliers() {
    return outliers;
  }

  public int[] getExpectedWordCount() {
    return expectedWordCount;
  }

  public int getMaxAmount() {
    return maxAmount;
  }

  public int getPaceExpectedTotal() {
    return paceExpectedTotal;
  }

  public int getCurrentDay() {
    return currentDay;
  }

  public int[] getWordcount() {
    return wordcount;
  }

  public int[] getAdjustedCount() {
    return adjustedCount;
  }
}
